每日一句來源:Daily English
Home is the place where, when you have to go there, they have to take you in. --無論何時何地,家永遠是向游子敞開大門的地方。
我們今天來說說要怎麼讓資料安全性上更加安全,並且加入身分的驗證,讓只有通過身分審核的成員可以讀取資料。
首先我們到我們firestore管理中心,點到規則
的頁簽,這是我們目前的規則,筆者這裡講解一下
service cloud.firestore {
match /databases/{database}/documents { //這裡指的是我們的整個firestore最大的document,我們不需要修改他
match /{document=**} { // 下面是就符合所有的document我們都給他可讀寫
allow read, write;
}
}
}
我們有兩種修該規則的方法
firebase deploy --only firestore:rules
筆者這邊直接使用平台,因為在檔案內,他是firestore.rules檔案,並沒有顏色,在觀看上不是很方便,筆者習慣在上面做修改,修改好在複製回專案。
所有方法都是由match
開始,
然後根據allow
的屬性給予權限
allow有幾種種類,以大分類來區分的話,分別有
read區分
write區分
接著我們看一個基本的權限防堵
接著我們測試看看防止沒有認證的使用者
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{message}{
allow read, write: if request.auth.uid != null;
}
}
}
上面這一段是否跟在寫functions有種熟悉的感覺呢?他們的邏輯有點像基本上也是透過{id}的方式找到list,而上面的意思是:messages這個collection底下的message只有當唯有認證的使用者才能通過。
關於auth的屬性,大家可以看官方的API文件,文件中有列出有哪些可用的屬性
https://firebase.google.com/docs/firestore/reference/security/?authuser=1#properties
當然我們可以把read、write切得更細,如下
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{message}{
allow read: if true;
allow write: if request.auth.uid != null;
}
}
}
如此就是只有登入的人可以寫,大家都能讀。
甚至你可以切得更細把寫的部分切開試試
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{roomId}{
allow read: if true;
allow create: if true;
allow update: if request.auth.uid != null;
allow delete: if request.auth.uid != null;
}
}
}
把使用者登出你會發現我們沒辦法修改、刪除了,只能新增,非常的酷!而且我們的程式內部完全不需做處理,只需在這裡加上就能防堵了。
這是一層的防賭,那如果是多層呢?一樣沒問題,我們可以一層一層的寫下來
我們直接使用users來實做
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid} {
allow read, write: if request.auth.uid == uid;
match /{document=**} {
allow read, write: if request.auth.uid == uid;
}
}
}
}
其中{document=**}這個寫法代表的是底下所有的collection、subcollections都套用這個判斷。
上面的意思是要讀寫users的資料時,必續request.auth.uid跟我們的uid為相同的才能讀取,並且底下的所有subcollections也是一樣要是相同的uid才可以,我們可以階層式的寫下來,當然我們也能直接在外部指定到,向下面這樣。
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid}/{document=**} {
allow read, write: if request.auth.uid == uid;
}
}
}
但是要注意上面這樣的話,在上層就完全沒防堵了,大家都能讀取。
接著我們除了auth的防堵外我們還能針對內容作判斷,我們用剛剛的message來做示範,如下
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{roomId}{
allow read: if true;
allow create: if true;
allow update: if resource.data.content == 'abc';
allow delete: if request.auth.uid != null;
}
}
}
只有當原本的資料是'abc'字串的時候才能修改,另外要注意creat的時候是沒有resource的。
那如果是送過去的資料的判斷呢?也沒問題,如下
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{roomId}{
allow read: if true;
allow create: if request.resource.data.content == '123';
allow update: if resource.data.content == 'abc';
allow delete: if request.auth.uid != null;
}
}
}
沒錯使用request底下的resource就是送過來的資料啦~上面就是當為字串123的時候才能接受。
除此之外我們還能針對其他的document的資料來做判斷,
我們有兩種方法
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{roomId}{
allow read: if true;
allow create: if request.resource.data.content == '123';
allow update: if resource.data.content == 'abc';
allow delete: if request.auth.uid != null &&
exists(/databases/$(database)/documents/users/$(request.auth.uid));
}
}
}
上面的刪除就是判斷資料是否存在,並且我們可以用&&或是||串接,其中/databases/$(database)/documents
這邊指的就是這個資料庫,跟最上面的match一樣,$()
這跟我們在js裡面的``中的${}是一樣的邏輯,就是把這個文字放進來,所以上面的意思就是,當有認證,並且這個認證的人的資料存在於資料庫,那就可以刪除。
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{roomId}{
allow read: if true;
allow create: if request.resource.data.content == '123';
allow update: if resource.data.content == 'abc';
allow delete: if request.auth.uid != null &&
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.uid == request.auth.uid;
}
}
}
上面指的就是取得這筆資料並且資料的內容符合條件,那就通過審核。
最後除了以上那些之外!我們甚至可以寫自己的判斷!
我們可以自己寫function來把判斷包裝起來,寫起來有點像js,以下展示
service cloud.firestore {
match /databases/{database}/documents {
function isAuthWithData() {
return request.auth.uid != null &&
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.uid == request.auth.uid;
}
match /messages/{roomId}{
allow read: if true;
allow create: if request.resource.data.content == '123';
allow update: if resource.data.content == 'abc';
allow delete: if isAuthWithData();
}
}
}
記得順序很重要,他是依序執行的,如果function放在後面會找不到喔,我們直接把剛剛的判斷搬上去,下面只要呼叫function就可以了,如此一來我們能夠把重複的判斷搬出去獨立起來,非常的方便!
最後有些細節要注意一下,
如果你在你的資料內有寫道像是allow update: if resource.data.content == 'abc';
這樣的內容,那當我們在query list的時候,如果你沒有下任何query條件,這個query是會被拒絕的,因為在驗證上不會通過,但是如果你在query的時候有下where("content", "==", "abc")
的話就會成功,像是下面這樣
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{post} {
allow read: if resource.data.owner_id == request.auth.uid;
}
}
}
db.collection("posts").orderBy("timestamp");
→ 這樣是會失敗的db.collection("posts").where("owner_id", "==", myUserId).orderBy("timestamp");
→ 這樣會成功
透過firestore的寫入驗證規則我們可以把很多驗證的邏輯擺在資料庫,一來能減輕我們client的負擔,二來是當我們有跨平台的時候,我們不須寫很多多餘的程式碼,透過資料讀寫的驗證直接達到防賭的功能,且firestore在驗證的部分可說是相當完整,也很容易操作!有了他真的為我們省了很多事。
今天由身分驗證作為結束!也是筆者這系列鐵人賽的最後一篇,透過firebase、Angular實做了一個聊天室,也了解到一個看似簡單的聊天室,其實裡面存在很多複雜的小邏輯的!雖然文章到這裡結束了,但是接下來的實際運用才是真正的開始,歡迎大家可以一起討論,一起成長,筆者真的認為如果我們的後端能如firebase這樣的realtime特性,並且擁有很高的效能,真的對我們前端開發是一個很不一樣的體驗,整個開發的邏輯也有了些許的不同,如果你沒有用過firebase你一定要用用看,不管你是後端還是前端的開發人員。用過realtime的特性,真的會讓你在設計上會有很多不同的邏輯與構思,期待與您再相遇。
https://firebase.google.com/docs/firestore/security/overview?authuser=1